package biz.datalk.industrialland.common.component.excpetion;

import biz.datalk.industrialland.common.exception.ApplicationException;
import biz.datalk.industrialland.common.json.CustomerJsonSerializer;
import biz.datalk.industrialland.common.result.UnifyResult;
import biz.datalk.industrialland.common.result.UnifyResultCode;
import biz.datalk.industrialland.common.util.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.springframework.http.MediaType;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;

/**
 * 异常处理器
 *
 * 只能处理 controller层中抛出的异常
 *
 * Filter中抛出异常，使用 {@link GlobalFilterExceptionHandler}
 *
 * 参考地址： https://docs.spring.io/spring/docs/5.1.12.RELEASE/spring-framework-reference/web.html#mvc-ann-exceptionhandler-args
 *
 * @author tarofang@163.com
 * @date 2019年08月12日
 */
@Slf4j
// @RestControllerAdvice
public class GlobalExceptionHandler {

    /** 处理自定义异常 */
    @ExceptionHandler(ApplicationException.class)
    //public UnifyResult handleApplicationException(ApplicationException ex, HandlerMethod handlerMethod) {
    public UnifyResult handleApplicationException(ApplicationException ex, HttpServletRequest request) {
        log.error("method:[{}] ------ ex:[{}] ------ ", getHandlerMethodName(SpringContextUtils.getRequestHandlerMethod(request)), ex.getMessage(), ex);
        return UnifyResult.fail(ex.getCode(), ex.getMessage());
    }

    /** 方法参数校验异常 */
    @SuppressWarnings("ConstantConditions")
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public UnifyResult handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
        if (log.isDebugEnabled()) {
            log.debug("method:[{}] ------ ex:[{}] ------ ", getHandlerMethodName(SpringContextUtils.getRequestHandlerMethod(request)), ex.getMessage());
        }
        return UnifyResult.fail(UnifyResultCode.VALIDATE_ERROR, ex.getBindingResult().getFieldError().getDefaultMessage());
    }

    /** token 过期 */
    @ExceptionHandler(AuthenticationException.class)
    public UnifyResult handlerShiroAuthenticationException(AuthenticationException ex, HttpServletRequest request) {
        log.error("method:[{}] ------ ex:[{}] ------ ", getHandlerMethodName(SpringContextUtils.getRequestHandlerMethod(request)), ex.getMessage(), ex);
        return UnifyResult.fail(UnifyResultCode.TOKEN_TIMEOUT, "长时间未登录，请从新登录");
    }

    /** 不支持的 Content-Type */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public UnifyResult handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex, HttpServletRequest request) {
        log.error("method:[{}] ------ ex:[{}] ------ ", getHandlerMethodName(SpringContextUtils.getRequestHandlerMethod(request)), ex.getMessage(), ex);
        return UnifyResult.fail(UnifyResultCode.METHOD_NO_SUPPORT_ERROR, "不支持该方式的请求");
    }

    @ExceptionHandler(ServletRequestBindingException.class)
    public UnifyResult handleServletRequestBindingException(ServletRequestBindingException ex, HttpServletRequest request) {
        log.error("method:[{}] ------ ex:[{}] ------ ", getHandlerMethodName(SpringContextUtils.getRequestHandlerMethod(request)), ex.getMessage(), ex);
        return UnifyResult.fail(UnifyResultCode.VALIDATE_ERROR, "参数绑定错误");
    }


    @ExceptionHandler(MultipartException.class)
    public UnifyResult handleMultipartException(Exception ex) {
        String message = ex.getMessage();
        String uri = SpringContextUtils.getHttpServletRequest().getRequestURI();
        log.error("method:[{}] ------ ex:[{}] ------ ", uri, message, ex);
        return UnifyResult.fail("未上传文件");
    }

    /** 其他异常 */
    @ExceptionHandler(Exception.class)
    public Object handleException(Exception ex, HttpServletResponse response, HttpServletRequest request) {
        String message = ex.getMessage();
        HandlerMethod handlerMethod = SpringContextUtils.getRequestHandlerMethod(request);
        log.error("method:[{}] ------ ex:[{}] ------ ", getHandlerMethodName(handlerMethod), message, ex);
        if (handlerMethod == null) {
            return UnifyResult.fail(UnifyResultCode.FAIL, "服务器发生错误，请稍后再试。");
        }

        Class<?> returnType = handlerMethod.getMethod().getReturnType();
        // 返回页面
        if (returnType.equals(ModelAndView.class)) {
            ModelAndView modelAndView = new ModelAndView("biz/datalk/framework/core/error/500");
            modelAndView.addObject("error", message);
            return modelAndView;
        }

        // 返回数据 不加密
        //if (!handlerMethod.hasMethodAnnotation(RSAES.class)) {
        //}

        String txt = "Required request body is missing";
        if (StringUtils.startsWithIgnoreCase(message, txt)) {
            print(response, UnifyResult.fail("请求参数丢失"));
            return null;
        }

        print(response, UnifyResult.fail(UnifyResultCode.FAIL, "服务器发生错误，请稍后再试。"));
        return null;
    }

    private void print(HttpServletResponse response, UnifyResult result) {
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try (PrintWriter pw = response.getWriter()) {
            CustomerJsonSerializer jsonSerializer = new CustomerJsonSerializer();
            pw.print(jsonSerializer.toJson(result));
            pw.flush();
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }
    }

    private String getHandlerMethodName(HandlerMethod handlerMethod) {
        if (handlerMethod == null) {
            return StringUtils.EMPTY;
        }
        Method method = handlerMethod.getMethod();
        String name = StringUtils.trimToEmpty(handlerMethod.getBeanType().getName());
        String mhName = StringUtils.trimToEmpty(method.getName());
        return StringUtils.isEmpty(name) ? mhName : name.concat("#").concat(mhName);
    }
}
